package simulation;

import jade.core.behaviours.Behaviour;
import jade.core.behaviours.CyclicBehaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

/**
 * Agent to carry on the Power Consumption.
 * @author Danielondon
 */
public class CarAgent extends SimulationAgent {
    protected StateOfCharge stateOfCharge;
    protected CarState carState;    
    protected ICarSpecs carSpecs;
    //protected IDrivingPattern drivingPattern;
    protected IChargeStrategy chargeStrategy;
    protected IMarket market;    
    private boolean isSimulationReadyToStart = false;
    private String dp_fileName;
    /**
     * Initialization of the Agent
     */
    @Override
    public void setup() {
        super.setup();
        //this.setCustomParameters(getArguments());
        this.initializeVariables();
        this.isSimulationStarted = false;        
        // Register the simulation service in the yellow pages
        // with the purpose to recieve messages of the simulation controller
        ServiceDirectory.registerAgentService(this, ServiceSimulationDescription.SIMULATION);
        
        // Adds the siulation behavior to communication with the Simulation Controller
        this.addBehaviour(new ControllerBehaviour());        
    }
	
    public StateOfCharge getStateOfCharge(){
        return this.stateOfCharge;
    }

    /**
     * Set custom arguments
     * @param arguments 
     */
    @Override
    protected void setCustomParameters(Object[] arguments) {       
        try {
            String fileName = (String) arguments[3];
            this.carSpecs = CarSpecsFactory.createCarSpecs(fileName);
            this.dp_fileName = (String)arguments[4];
            //this.drivingPattern = DrivingPatternFactory.createDrivingPattern(fileName, this.TOTAL_SLOTS, this.SLOT_SIZE);
            String strategyName = (String)arguments[5];
            this.chargeStrategy = ChargeStrategyFactory.createChargeStrategy(strategyName, this.SLOT_SIZE, this.carSpecs.getBrand());
            this.addBehaviour(new RequestMarketData());
        } catch (Exception ex) {
            logger.error(ex);
            this.doDelete();
        }
    }
    
    private void initializeVariables(){
        this.carState = CarState.CONNECTED;
        this.stateOfCharge = new StateOfCharge(carSpecs.getBatteryCapacity());
    }
    
    protected String createFileName(String filename, int i) {
        return CommonResources.replaceText(filename, "1", i + "");
    }
		
    /**
     * Handles the Simulation Behavior
     */
    private class ControllerBehaviour extends CyclicBehaviour {
         public void action() {
            MessageTemplate mt = MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.AGREE), 
                    MessageTemplate.MatchPerformative(ACLMessage.REQUEST));
            ACLMessage msg = myAgent.receive(mt);
            
            if (msg != null) {
                if (msg.getPerformative() == ACLMessage.AGREE) {                    
                    if (!isSimulationStarted && isSimulationReadyToStart) {
                        initializeVariables();
                        isSimulationStarted = true;                        
                        notifySimulationController(CommonResources.SIMULATION_CONVERSATION_ID, createMessage("Starting Simulation"));
                        Behaviour simulationBehaviour;
                        try {
                            boolean batchSimulation = false;
                            if (batchSimulation) {
                                for (int i = 0; i < 100; i++){
                                    simulationBehaviour = new SimulationBehaviour(createFileName(dp_fileName,i+1));
                                    this.myAgent.addBehaviour(simulationBehaviour);
                                }
                            } else {
                                simulationBehaviour = new SimulationBehaviour(dp_fileName);
                                this.myAgent.addBehaviour(simulationBehaviour);
                            }
                            
                        } catch (Exception ex) {
                            logger.error(ex);
                            doDelete();
                        }
                        
                    } else {
                        notifySimulationController(CommonResources.SIMULATION_CONVERSATION_ID, createMessage("Simulation could not start."));
                    }
                } else if (msg.getPerformative() == ACLMessage.REQUEST) {
                    Object [] parameters = CommonResources.ParametersToObjectArray(msg.getContent());
                    setParameters(parameters);
                    //setCustomParameters(parameters);
                }
            }
            else {
                block();
            }
	}        
    }  // End of inner class SimulationBehavior
       
    /**
     * Timer behavior for simulation.
     */
    private class SimulationBehaviour extends OneShotBehaviour{
        private IDrivingPattern drivingPattern;
        
        public SimulationBehaviour(String filename) throws Exception{
            logger.info("Creating Pattern " + filename);
            this.drivingPattern = DrivingPatternFactory.createDrivingPattern(filename, TOTAL_SLOTS, SLOT_SIZE);
        }

        @Override
        public void action() {
            SimulationResult result;
            try {
                result = simulateConsumption();
                isSimulationStarted = false;
                String message = "OK";
                if (!result.isFeasible()){
                    message = "UNFEASIBLE";
                }
                notifySimulationController("endSimulation", createMessage(message) + "&&&" + result.toString());
            } catch (UnfeasibleException ex) {
                isSimulationStarted = false;
                notifySimulationController(CommonResources.SIMULATION_CONVERSATION_ID, createMessage("Simulation FAILED "+ ex.getMessage()));
            }
        }
        
        private SimulationResult simulateConsumption() throws UnfeasibleException{
            stateOfCharge.setValue(carSpecs.getBatteryCapacity());        
            SimulationResult simulationResult = chargeStrategy.getResult(market, this.drivingPattern, stateOfCharge, carSpecs, TOTAL_SLOTS);
            if (simulationResult == null){
                throw new UnfeasibleException("Results were not calculated");
            }
            return simulationResult;
        }
    }
   
    private String createMessage(String comments){
        // ID ; Charge ; State ; Slot Count ; Simulation Status
        String message =  this.market.getName() + CommonResources.STRING_SEPARATOR +
                carState + CommonResources.STRING_SEPARATOR + 
                TOTAL_SLOTS + CommonResources.STRING_SEPARATOR +
                isSimulationStarted + CommonResources.STRING_SEPARATOR +
                comments + CommonResources.STRING_SEPARATOR + 
                carSpecs.getBrand() + "-" + carSpecs.getModel();

        return message;
    }
    
    
    private void notifySimulationController(String conversationId, String message) {
        ACLMessage cfp = new ACLMessage(ACLMessage.INFORM);
        cfp.addReceiver(controllerAgent);
        cfp.setContent(message);
        cfp.setConversationId(conversationId);
        cfp.setReplyWith("inform"+System.currentTimeMillis()); // Unique value
    
        //logger.info("Sending message " + message);
        this.send(cfp);

    }
    
    
    /**
     * Behavior to manage the behvior on each slotinquiry the price
     */
    private class RequestMarketData extends Behaviour {
        private int step = 0;
        private MessageTemplate mt;
        
        @Override
        public void action(){
            //if (marketAgents.length == 0){
            //    step = 2;
            //}
            if (marketAgent == null){
                step = 2;
            }
            switch (step) {
                case 0:
                    // Send the cfp to all markets
                    ACLMessage cfp = new ACLMessage(ACLMessage.CFP);
                    cfp.addReceiver(marketAgent);
                    cfp.setContent("marketData");
                    cfp.setConversationId("marketData");
                    cfp.setReplyWith("cfp"+System.currentTimeMillis()); // Unique value
                    myAgent.send(cfp);
                    // Prepare the template to get proposals
                    mt = MessageTemplate.and(MessageTemplate.MatchConversationId(cfp.getConversationId()),
                                    MessageTemplate.MatchInReplyTo(cfp.getReplyWith()));
                    step = 1;                                
                    break;
                case 1:
                    ACLMessage reply = myAgent.receive(mt);
                    if (reply != null) {
                        String content = reply.getContent();                        
                        try {
                            market = new Market(content, CommonResources.STRING_SEPARATOR, TOTAL_SLOTS, SLOT_SIZE, CommonResources.DEFAULT_CURRENCY);
                            isSimulationReadyToStart = true;
                        } catch (Exception ex) {
                            logger.error(ex);
                        }
                        step = 2;
                    } else {
                        block();
                    }
                    break;
            }
        }
        
        /**
         * Checks if the behavior is completed.
         * If its done, it will terminate, otherwise it will re-execute the action method.
         * @return 
         */
        @Override
        public boolean done(){
            boolean isDone = step == 2;
            return isDone;
        } 
    }
}
